Realizamos un análisis de datos sobre una base con registros de quejas de productos financieros en Estados Unidos.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import geopandas as gpd
import datetime
from sklearn.cluster import DBSCAN
from wordcloud import WordCloud
import editdistance
import time
from nltk.stem import PorterStemmer
from nltk.corpus import stopwords
palabras_inutiles = stopwords.words('english')
from sklearn.naive_bayes import BernoulliNB
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
from sklearn.feature_extraction.text import CountVectorizer
import warnings
warnings.filterwarnings("ignore")
pd.set_option('display.max_rows',None)
Para nuestro ejercicio ocupamos:
df=pd.read_csv('inputs/Consumer_Complaints.csv')
df.head()
mapa=gpd.read_file('inputs/cb_2018_us_state_5m')
mapa.head()
zip_data=pd.read_csv('inputs/zip_code_database.csv')[['zip','latitude','longitude']]
zip_data.head()
df['company_0']=df['Company'].copy().str.upper()
df['company_0']=df['company_0']\
.str.replace(' C\.O','').str.replace(' P\.A','').str.replace(' CO\.','').str.replace('PA\.','')
df['company_0']=df['company_0']\
.str.replace('\.','').str.replace(',','').str.replace(' ',' ')
df['company_0']=df['company_0']\
.str.replace(' INC','').str.replace(' LLC','').str.replace(' LLP','')\
.str.replace(' PLC','').str.replace(' PLLC','').str.replace(' LLP','')\
.str.replace(' PS','').str.replace(' LC','').str.replace(' PL','').str.replace(' SA RL','').str.replace(' LTD','')\
.str.replace(' LP','').str.replace(' PC','')
df['zip']=pd.to_numeric(df['ZIP code'],errors='coerce',downcast='integer')
df['zip']=df['zip'].astype('int64',errors='ignore')
df['date']=df['Date received'].apply(lambda x: datetime.datetime.strptime(x,'%m/%d/%Y'))
df['date_year']=df['date'].dt.strftime('%Y-%m')
df=df.merge(zip_data,on='zip',how='left')
puntos = gpd.GeoDataFrame(df[['Complaint ID','State','Product','latitude','longitude','zip']],
geometry=gpd.points_from_xy(df['longitude'],
df['latitude']))
#puntos=puntos[puntos.is_valid]
Primero volvemos a mostrar una parte de la base para darnos una idea rápida de la información que contiene. En ella podemos observar que hay información de identificación espacial y temporal.
Además de esto, cada registro tiene información sobre el producto, el banco proveedor, y variables sobre el seguimiento del caso.
Posterior a esto pasaremos a ver el número de registros únicos por proveedor, con el objetivo de ver a grandes rasgos que tan factible es scsdcsdcsd, y el número de registros ocn información faltante para cada categorÃa, para tenerlo en consideración.
df.head()
df.nunique()
df.isnull().sum().sort_values(ascending=False)/df.shape[0]*100
De aquà salta a la vista que únicamente 17% de los registros contienen comentarios. Desplegando la información a lo largo del tiempo, podemos observar que todos esos registros son de los últimos dos años. Es probable que hasta ese momento se haya dado la opción de registrar comentarios, y que de ahora en adelante se conserve dicha funcionalidad.
plt.figure(figsize=(20,10))
plt.grid(axis='x')
plt.plot(df[df['Consumer complaint narrative'].isnull()].groupby('date_year').size(),label='null')
plt.plot(df[df['Consumer complaint narrative'].notnull()].groupby('date_year').size(),label='notnull')
plt.xlabel('date_year')
plt.xticks(['2012-01','2013-01','2014-01','2015-01','2016-01','2013-10'],['2012','2013','2014','2015','2016'])
plt.title('Consumer complaint narrative - null values')
plt.legend()
plt.show()
Ahora pasamos a revisar las métricas de seguimiento de los casos. Volviendo al porcentaje de registros sin información, vemos que todos ellos, salvo "Company public response", tienen información para casi la totalidad de registros, por lo que nos pueden dar una imagen completa de las cosas.
Si bien los gráficos pie tienen defectos de percepción, son útiles cuando hay pocas categorÃas o el mayor porcentaje de casos se concentra en pocas de ellas.
plt.figure(figsize=(15,15))
ax = plt.subplot(2,2,1)
df.groupby('Timely response?').size().plot('pie',ax=ax)
ax.set_title('Timely response?')
ax.set_ylabel(None)
ax = plt.subplot(2,2,2)
df.groupby('Company response to consumer').size().plot('pie',ax=ax)
ax.set_title('Company response to consumer')
ax.set_ylabel(None)
ax = plt.subplot(2,2,3)
df.groupby('Consumer disputed?').size().plot('pie',ax=ax)
ax.set_title('Consumer disputed?')
ax.set_ylabel(None)
ax = plt.subplot(2,2,4)
df.groupby('Company public response').size().plot('pie',ax=ax)
ax.set_title('Company public response')
ax.set_ylabel(None)
plt.show()
Dado que las variables "Company response to consumer" y "Consumer disputed?" se ven sospechosamente similares, pasamos a examinar con mayor cuidado la composición conjunta de los registros entre estas categorÃas en caso de que exista una correlación. Podemos observar que no es el caso.
Por último, desplegamos los registros en el tiempo para evaluar la posibilidad de que la mayorÃa de las quejas se encuentren concentradas en algún punto del tiempo y esto pueda reflejar un deterioro en el servicio. Los datos no apoyan esta hipótesis, pues vemos que la distribución de ambos casos se mantienen estables en el tiempo.
df.groupby('Consumer disputed?')['Company response to consumer'].value_counts(normalize=True)*100
plt.figure(figsize=(20,10))
plt.grid(axis='x')
aux=df.groupby('Consumer disputed?').size().sort_values(ascending=False).index
for i in range(len(aux)):
plt.plot(df[df['Consumer disputed?']==aux[i]].groupby('date_year').size(),label=aux[i])
plt.xlabel('date_year')
plt.xticks(['2012-01','2013-01','2014-01','2015-01','2016-01'],['2012','2013','2014','2015','2016'])
plt.title('Consumer disputed?')
plt.legend()
plt.show()
Para empezar, es claro que la tendencia de registros ha sido a la alza de manera constante. La caÃda en el último punto probablemente se deba a una falta de registros para el mes más reciente, ocasionados por una fecha de corte
plt.figure(figsize=(20,10))
plt.grid(axis='x')
plt.plot(df.groupby('date_year').size())
plt.xlabel('date_year')
plt.xticks(['2012-01','2013-01','2014-01','2015-01','2016-01'],['2012','2013','2014','2015','2016'])
plt.title('Consumer disputed?')
plt.show()
Pasando a ver la evoluciónede registros segmentados por producto y por estado, también es claro que no hay grandes cambios en tendencias, apuntando a que los productos se encuentran establecidos en el mercado desde hace tiempo (de lo contrario podrÃamos ver una tendencia a la alza de los introducidos recientemente), mientras que con los estados esto significa que no ha habido una expansión especial en ninguno de ellos o, en su caso, una alza especial de quejas por mal servicio en una zona en particular.
plt.figure(figsize=(20,10))
plt.grid(axis='x')
aux=df.groupby('Product').size().sort_values(ascending=False).index
for i in range(len(aux)):
plt.plot(df[df['Product']==aux[i]].groupby('date_year').size(),label=aux[i])
plt.xlabel('date_year')
plt.xticks(['2012-01','2013-01','2014-01','2015-01','2016-01'],['2012','2013','2014','2015','2016'])
plt.title('Product')
plt.legend()
plt.show()
plt.figure(figsize=(20,10))
plt.grid(axis='x')
aux=df.groupby('State').size().sort_values(ascending=False).index
for i in range(len(aux)):
plt.plot(df[df['State']==aux[i]].groupby('date_year').size(),label=aux[i])
plt.xlabel('date_year')
plt.xticks(['2012-01','2013-01','2014-01','2015-01','2016-01'],['2012','2013','2014','2015','2016'])
plt.title('State')
#plt.legend()
plt.show()
En la gráfica de barras pácticamente no aparecen registros para monedas virtuales. Mostrando el conteo de observaciones, vemos que únicamente hay dos registros para dicha categorÃa. Con tan pocas observaciones, lo mejor será sacarla de los registros a la hora de ejecutar algoritmos de clasificación, debido a que no hay suficiente información para proveer resultados estadÃsticos plausibles para dicha categorÃa, y podrÃa pensarse algo similar de la segunda categorÃa más pequeña.
df.groupby('Product').size().sort_values()\
.plot(kind='barh',figsize=(10,5),color='darkblue')
plt.show()
df.groupby('Product').size().sort_values(ascending=False)
Pasando a un análisis más profundo en el ámbito geográfico, podemos observar que una gran cantidad de registros se concentran en los primeros 4 estados. Sin embargo, hay que recordar que para este caso el tamaño y la densidad de población son los factores clave que determinan la distribución de casos.
California, Florida y Texas (los 3 primeros) son de los pocos estados que cubren un gran territorio y poseen zonas densamente pobladas. Por otra parte, el centro del paÃs está relativamente vacÃo, a pesar de componerse por territorios de tamaño considerable. Estos cubren los estados con el menor número de registros. Por úlitmo, los estados de la costa este se encuentran a la mitad de la distribución, debido a que están llenos de zonas densamente pobladas pero son de tamaño pequeño.
df.groupby('State').size().sort_values()\
.plot(kind='bar',figsize=(20,8),color='darkblue')
plt.show()
aux=df.groupby('State').size().reset_index().rename(columns={0:'total_quejas_edo','State':'STUSPS'})
mapa=mapa.merge(aux,on='STUSPS')
del aux
# omitimos las entidades fuera de la masa continental principal para la gráfica
mapa[~mapa['STUSPS'].isin(['GU','MP','PR','HI','AK','AS','VI'])]\
.plot(column='total_quejas_edo',cmap='Blues',figsize=(40,30),edgecolor='black')
#plt.grid(b=None)
plt.show()
Ahora pasamos a visualizar los datos de manera más desagregada. De esta manera se puede observar puntos de concentración de quejas en lo que probablemente sean poblaciones urbanas. Descomponiendo la información aún más, ahora con un mapa por registro, vemos que la distribución de quejas a lo largo del paÃs no varÃa por producto:
f, ax = plt.subplots(1,figsize=(40,30))
# Como el mapa incluye territorios fuera del conjunto principal (el más conocido Alaska, incluyendo otros desde Hawaii hasta Guam)
# los excluimos para la visualizacion
aux_territorio_princ = ~mapa['STUSPS'].isin(['GU','MP','PR','HI','AK','AS','VI'])
mapa[aux_territorio_princ].plot(ax=ax,color='white',edgecolor='black')
del aux_territorio_princ
# para excluir los puntos fuera del conjunto principal excluimos los puntos fuera de cierto intervalo de coordenadas
puntos.loc[(puntos['latitude']>22)&(puntos['latitude']<55)&(puntos['longitude']>-135)&(puntos['longitude']<-60)]\
.plot(ax=ax,color='red',markersize=1,alpha=.1)#03)
plt.show()
productos= df.groupby('Product').size().sort_values(ascending=False).index
plt.figure(figsize=(40,60))
for i in range(len(productos)):
ax = plt.subplot(6, 2, i + 1)
# mapa
mapa[~mapa['STUSPS'].isin(['GU','MP','PR','HI','AK','AS','VI'])]\
.plot(ax=ax,color='white', edgecolor='grey')
# puntos
puntos.loc[(puntos['latitude']>22)&(puntos['latitude']<55)&
(puntos['longitude']>-135)&(puntos['longitude']<-60)&
(puntos['Product']==productos[i])]\
.plot(ax=ax,color='red',markersize=1,alpha=1)
ax.set_title(productos[i])
ax.set_aspect('equal', adjustable='datalim')
plt.show()
Para la identificación de zonas coconcentradoras de quejas, procedimos a ejecutar un algoritmo de aprendizaje no supervisado, especÃficamente una versión modificada del algoritmo BDSCAN.
Dado que hacemos la clusterización sobre las coordenadas de las quejas, las cuales provienen de su código zip, y naturalmente hay varios casos por zip, usamos esto a nuestro favor para ahorrar tiempo en el cálculo. En vez de aplicar el algoritmo a cada registro, lo aplicamos a cada codigo zip, tomando el número de quejas registradas en dicho código zip como peso.
Elección de parámetros
# clasificación no supervisada
# conteo de registros por zip
puntos_0=puntos.groupby(['latitude','longitude']).size().reset_index().rename(columns={0:'conteo'})
# BDScan pesado por registros por zip
clustering = DBSCAN(eps=1,
min_samples=2000)\
.fit(np.array(puntos_0[['longitude','latitude']]),
sample_weight=puntos_0['conteo'])
puntos_0['etiqueta']=clustering.labels_
puntos_0=puntos[['geometry','longitude','latitude']]\
.drop_duplicates()\
.merge(puntos_0,on=['longitude','latitude'])
##########################################
# gráfica
f, ax = plt.subplots(1,figsize=(40,30))
# Como el mapa incluye territorios fuera del conjunto principal (el más conocido Alaska, incluyendo otros desde Hawaii hasta Guam)
# los excluimos para la visualizacion
aux_territorio_princ = ~mapa['STUSPS'].isin(['GU','MP','PR','HI','AK','AS','VI'])
mapa[aux_territorio_princ].plot(ax=ax,color='white',edgecolor='black')
del aux_territorio_princ
# para excluir los puntos fuera del conjunto principal excluimos los puntos fuera de cierto intervalo de coordenadas
#puntos.loc[(puntos['latitude']>22)&(puntos['latitude']<55)&(puntos['longitude']>-135)&(puntos['longitude']<-60)]\
# .plot(ax=ax,color='red',markersize=1,alpha=.1)#03)
aux_territorio_princ_0 = (puntos_0['latitude']>22)&(puntos_0['latitude']<55)&(puntos_0['longitude']>-135)&(puntos_0['longitude']<-60)
puntos_0[aux_territorio_princ_0]\
.plot(ax=ax,
column=puntos_0.loc[aux_territorio_princ_0,'etiqueta'],
markersize=2,
alpha=.5)
plt.show()
Podemos notar que los clusters correspondÃan a regiones amplias, como uno que comprende toda la costa noreste. Con la esperanza de identificar clusters por ciudades y sus áreas metropolitanas, tomamos un caso con valores más pequeños. Partiendo de un mÃnimo de elelmentos de 500 por cluster, calculamos la distancia promedio entre cada elemento y sus 500 vecinos. Ordenándolos , observamos que se da un punto de inflexión en 0.25 aproximadamente. Tomamos dicho valor como epsilon y procedemos a ejecutar el algoritmo, con resultados favorables.
from sklearn.neighbors import NearestNeighbors
neighbors = NearestNeighbors(n_neighbors=500)
neighbors_fit = neighbors.fit(np.array(puntos_0[['longitude','latitude']]))
distances, indices = neighbors_fit.kneighbors(np.array(puntos_0[['longitude','latitude']]))
distances = np.sort(distances, axis=0)
distances = distances[:,1]
plt.plot(distances)
plt.show()
plt.plot(distances[:23340])
plt.show()
# clasificación no supervisada
# conteo de registros por zip
puntos_0=puntos.groupby(['latitude','longitude']).size().reset_index().rename(columns={0:'conteo'})
# BDScan pesado por registros por zip
clustering = DBSCAN(eps=.25,
min_samples=500)\
.fit(np.array(puntos_0[['longitude','latitude']]),
sample_weight=puntos_0['conteo'])
puntos_0['etiqueta']=clustering.labels_
puntos_0=puntos[['geometry','longitude','latitude']].drop_duplicates()\
.merge(puntos_0,on=['longitude','latitude'])
##########################################
# gráfica
f, ax = plt.subplots(1,figsize=(40,30))
# Como el mapa incluye territorios fuera del conjunto principal (el más conocido Alaska, incluyendo otros desde Hawaii hasta Guam)
# los excluimos para la visualizacion
aux_territorio_princ = ~mapa['STUSPS'].isin(['GU','MP','PR','HI','AK','AS','VI'])
mapa[aux_territorio_princ].plot(ax=ax,color='white',edgecolor='black')
del aux_territorio_princ
# para excluir los puntos fuera del conjunto principal excluimos los puntos fuera de cierto intervalo de coordenadas
#puntos.loc[(puntos['latitude']>22)&(puntos['latitude']<55)&(puntos['longitude']>-135)&(puntos['longitude']<-60)]\
# .plot(ax=ax,color='red',markersize=1,alpha=.1)#03)
aux_territorio_princ_0 = (puntos_0['latitude']>22)&(puntos_0['latitude']<55)&(puntos_0['longitude']>-135)&(puntos_0['longitude']<-60)
puntos_0[aux_territorio_princ_0]\
.plot(ax=ax,
column=puntos_0.loc[aux_territorio_princ_0,'etiqueta'],
markersize=2,
alpha=.5)
plt.show()
company=df['company_0'].drop_duplicates().values.tolist()
# computamos una especie de matriz de distancias, normalizado por la longitud de la palabra mas chica
# dado que es simetrica, solo calculamos la mitad triangular superior
start = time.time()
dist_mat=np.ones((len(company),len(company)))-np.identity(len(company))
for i in (range(len(company))):
for j in (range( i+1,len(company) ) ):
# for j in (range(len(company)) ):
dist_mat[i,j]=editdistance.eval(company[i],company[j])/min(len(company[i]),len(company[j]))
end = time.time()
print((end - start)/60)
start = time.time()
# total es el total de elementos en la lista
# le iremos quitando elementos en cada iter hasta vaciarlo
def list_diff(li1,li2):
r=[i for i in li1 if i not in li1 or i not in li2]
return r;
vv=[]
total=list(range(len(company)))
max_dist=.25
while ( len(total)>0 ):
# tomamos el primer elemento de total y con ello el cjto inicial de elementos parecidos a este
v=np.where(dist_mat[total[0],:]<max_dist)[0].tolist()
v=list_diff(v,list(range(total[0])))
# v=dist_mat[total[0],dist_mat[total[0],:]<.25]
if (len(v)>1):
i=0
limite=len(v)-1
# de manera iterativa, si el elemento parecido tiene elementos parecidos, los añadimos al conjunto
# en la cola, de esta forma posteriormente evaluaremos si estos a su vez tienen elementos similares
# o no. acto seguido actualizamos el limite, puesto que el conjunto creció, y pasamos el siguiente
# elemento parecido
# cuando agotemos los elementos similares alcancaremos el limite y terimnaremos con un conjunto v
# donde todos los elementos en v son similares entre si y no hay ninguno afuera parecido a ellos
while (i<=limite):
a = np.where(dist_mat[v[i],:]<max_dist)[0].tolist()
a = list_diff(a,[v[i]])
a = list_diff(a,v)
if ( len(a)>0 ) :
v+=a
lim=len(v)
i+=1
# una vez hecho esto añadimos este conjunto v a la lsita de conjuntos vv y eliminamos sus elementos
# de la lista total
# notese como en la siguiente iteracion total[0] tomará otro valor
# de cierta forma, eliminar los elementos de v de total ejecuta una especie de for indirecto
v=list_diff(v,list(range(total[0])))
if( len(v)>1 ):
vv.append(v)
total=list_diff(total,v)
end = time.time()
print((end - start)/60)
A continuación mostramos los bancos agrupados (nótese como solo imprimimos una parte para evitar saturar):
vvv=[]
for j in range(len(vv)):
vvv.append([company[i] for i in vv[j]])
vvv[:5]
#flat_list = []
#for vv in vvv:
# for bank in vv:
# flat_list.append(bank)
#
#flat_list_0 = []
#for vv in vvv:
# for i in range(len(vv)):
# flat_list_0.append(vv[0])
#
#company_norm=pd.DataFrame({'company_0':flat_list,'company_1':flat_list_0})
#
#df.merge(company_norm,on='company_0',how='left')\
# .groupby('company_1').size().sort_values()\
# .plot(kind='barh',figsize=(5,50),color='darkblue')
#plt.show()
Para obtener visualizaciones del texto adjunto a los registros, este pasará por un procesamiento en donde eliminaremos palabras triviales, signos de puntuación, entre otras cosas. Primero aplicaremos un algoritmo de stemming, el cual quita los sufijos a las palabras para homogeneizarlas, ya que con esto se les despoja de sus conjugaciones. Este ejercicio es algo tardado (el comando tardó casi 6 min en ejecutarse).
comentarios=df[df['Consumer complaint narrative'].notnull()]['Consumer complaint narrative'].copy().str.lower()
st = PorterStemmer()
start = time.time()
comentarios_stem = []
for comentario in comentarios:
comentarios_stem.append(" ".join([st.stem(i) for i in comentario.split()]))
end = time.time()
print((end - start)/60)
df['text']=df['Consumer complaint narrative'].copy()
df.loc[df['text'].notnull(),'text']=comentarios_stem
df.loc[df['text'].notnull(),'text']=df.loc[df['text'].notnull(),'text']\
.str.lower()\
.str.replace('{.+}','').str.replace('xxxx','').str.replace('\.','').str.replace('\,','')\
.str.replace('\(','').str.replace('\)','')\
.str.replace(' ',' ').str.replace(' ',' ').str.replace(' ',' ')
Una vez procesado el texto, mostraremos la nube de palabras para cada producto, en donde se toman las palabras más comunes para cada categorÃa:
productos=df['Product'].drop_duplicates().tolist()
plt.figure(figsize=(20,40))
for i in range(len(productos)):
ax = plt.subplot(len(productos)/2, 2, i + 1)
# genera base de variables indicadoras por palabra
#def base_vec_palabras(OPS,col_palabras,col_animo,animo):
# V es una matriz
vectorizer=CountVectorizer()
V = vectorizer.fit_transform(df[df['text'].notnull()][df['Product']==productos[i]]['text'].values.astype('U'))
# lista de palabras
lista_palabras = vectorizer.get_feature_names()
# conteo de palabras
conteo_lista_palabras=pd.Series(V.sum(axis=0).tolist()[0],index=lista_palabras)
conteo_lista_palabras_3 = conteo_lista_palabras.iloc[np.where(pd.Series(
[len(omg) for omg in conteo_lista_palabras.index.values.tolist()]
)>2)]
# subset de utiles
lista_palabras_subset = list(
set(conteo_lista_palabras_3.index.values.tolist()
).difference(
set(palabras_inutiles)
).difference(
set(['i','thi','&'])
))
freqs=conteo_lista_palabras_3[lista_palabras_subset]
wc=WordCloud(width=3000,height=2000,max_words=30,random_state=1,
prefer_horizontal=1,collocations=False,stopwords=None)\
.generate_from_frequencies(freqs.sort_values(ascending=False)[:30])
ax.imshow(wc)
ax.axis('off')
ax.set_title(productos[i])
plt.tight_layout()
plt.show()
del productos
Para la clasificación de registros de acuerdo al texto usaremos el modelo de Naive Bayes con distribución Bernoulli. Este es conveniente dado que funciona bien con datos con observaciones escasas en una gran cantidad de variables (sparse matrix), donde aparte tomamos cada ocurrencia en un registro como una variable binaria.
En ella quitaremos las palabras cortas (con menos de 3 caracteres) o consideradas inútiles (por ejemplo preposiciones) para mejorar nuestras predicciones.
Además de esto, omitiremos las categorÃas de criptomonedas y otros servicios financieros, ya que casi no tienen observaciones.
# V es una matriz
vectorizer=CountVectorizer()
categorias_pequeñas=['Virtual currency','Other financial service']
V = vectorizer.fit_transform(df[df['text'].notnull()][~df['Product'].isin(categorias_pequeñas)]['text']\
.values.astype('U'))
V = np.ceil(V/1000)
# lista de palabras
lista_palabras = vectorizer.get_feature_names()
# conteo de palabras
conteo_lista_palabras=pd.Series(V.sum(axis=0).tolist()[0],index=lista_palabras)
conteo_lista_palabras_2 = conteo_lista_palabras#.where(conteo_lista_palabras>=20).dropna()
conteo_lista_palabras_3 = conteo_lista_palabras_2.iloc[np.where(pd.Series(
[len(omg) for omg in conteo_lista_palabras_2.index.values.tolist()]
)>2)]
# subset de utiles
lista_palabras_subset = list(
set(conteo_lista_palabras_3.index.values.tolist()
).difference(
set(palabras_inutiles)
).difference(
set(['i','thi','&'])
))
freqs=conteo_lista_palabras_3[lista_palabras_subset]
X=V[:,np.where(conteo_lista_palabras[conteo_lista_palabras[lista_palabras_subset]])[0]]
y=df[df['text'].notnull()][~df['Product'].isin(['Virtual currency','Other financial service'])]['Product']
#for j in [0.03125,0.0625,0.125,0.25,0.5,1,2,4,8,16]:
for j in np.geomspace(1/2**4,2**4,4+4+1):
modelo = BernoulliNB(alpha=j)
# by_multi.fit(arreglo_train,OPSS['razon_animo'][:30000])
# prediccion=by_multi.predict(arreglo_test).tolist()
aux=cross_val_score(modelo,
X, y, cv=10)
sns.distplot(aux)
print( 'Accuracy promedio con alpha=' + str(j) + ': %' + str(100*aux.mean()))
del aux
# conteo = 0
# for i in range(len(real)):
# if real[i]==prediccion[i]:
# conteo+=1
# print(conteo)
El mejor score se encuentra alrededor de alpha=0.125, por lo que nos quedamos con ese valor. Como este ejercicio solo se va a ocupar para los datos ya observados y no para predicción, tomamos la muestra completa en la estimación final.
from yellowbrick.classifier import ROCAUC
by_multi = BernoulliNB(alpha=.125)
by_multi.fit(X,y)
#fig, ax = plt.subplots(figsize=(6, 6))
roc_viz = ROCAUC(by_multi)
roc_viz.score(X, y)
roc_viz.poof()
plt.show()
by_multi.score(X,y)
from yellowbrick.classifier import ConfusionMatrix
cm_viz = ConfusionMatrix(by_multi)
cm_viz.score(X,y)
cm_viz.poof()
plt.show()
prediccion=by_multi.predict(X).tolist()
pd.DataFrame({'real':y,'predicted':prediccion}).head()